#if !LITE
using System;
using System.Text;
using System.IO;
using System.Collections;

using Word = Microsoft.Office.Interop.Word;
using Core = Microsoft.Office.Core;
using iTextSharp.text;

namespace iPdf
{
    class DocConverter: OfficeConverter
    {
        #region Fields
        private Word.Application word ;
        private Word.Document document;
        
        private const Word.WdInformation wdFirstLineNo = Word.WdInformation.wdFirstCharacterLineNumber;
        private const Word.WdInformation wdHorizontalPos = Word.WdInformation.wdHorizontalPositionRelativeToPage;
        private const Word.WdInformation wdVerticalPos = Word.WdInformation.wdVerticalPositionRelativeToPage;
        private const Word.WdInformation wdEndPageNo = Word.WdInformation.wdActiveEndPageNumber;
        private const Word.WdGoToItem wdGoToPage = Word.WdGoToItem.wdGoToPage;
        private const Word.WdGoToItem wdGoToLine = Word.WdGoToItem.wdGoToLine;
        private const Word.WdGoToDirection wdGoToPrevious = Word.WdGoToDirection.wdGoToPrevious;
        private const Word.WdGoToDirection wdGoToNext = Word.WdGoToDirection.wdGoToNext;
        #endregion

        #region Properties

        public override FileFormat Format
        {
            get
            {
                return FileFormat.WordFormat;
            }
        }

        public override string ApplicationName
        {
            get
            {
                return "Microsoft Word";
            }
        }


        private int SelectionLineNo
        {
            get
            {
                return (int)word.Selection.get_Information(wdFirstLineNo);
            }
        }

        private int SelectionPageNo
        {
            get
            {
                return (int)word.Selection.get_Information(wdEndPageNo);
            }
        }

        private float SelectionHorizontalPosition
        {
            get
            {
                return (float)word.Selection.get_Information(wdHorizontalPos);
            }
        }

        private float SelectionVerticalPosition
        {
            get
            {
                return (float)word.Selection.get_Information(wdVerticalPos);
            }
        }

        #endregion

        #region Methods

        #region Helpers methods
        private void GoToPrevious(object gotoItem)
        {
            GoToPrevious(gotoItem, Global.NullObject);
        }

        private void GoToPrevious(object gotoItem, object gotoCount)
        {
            object gotoDirection = Word.WdGoToDirection.wdGoToPrevious;
            word.Selection.GoTo(ref gotoItem, ref gotoDirection, ref gotoCount, ref Global.NullObject);
        }

        private void GoToNext(object gotoItem)
        {
            GoToNext(gotoItem, Global.NullObject);
        }

        private void GoToNext(object gotoItem, object gotoCount)
        {
            object gotoDirection = Word.WdGoToDirection.wdGoToNext;
            word.Selection.GoTo(ref gotoItem, ref gotoDirection, ref gotoCount, ref Global.NullObject);
        }

        private Word.Range GetRangeAt(int charIndex)
        {
            object start = charIndex;
            return document.Range(ref start, ref start);
        }

        private Word.Range GoToCharAt(int charIndex)
        {
            Word.Range range = GetRangeAt(charIndex);
            range.Select();
            return range;
        }

        private void MoveCursorToLineEnd()
        {
            MoveCursorToLineEnd(false);
        }

        private void MoveCursorToLineEnd(bool extend)
        {
            object moveUnit = Word.WdUnits.wdLine;
            object moveType = Word.WdMovementType.wdMove;
            if (extend)
                moveType = Word.WdMovementType.wdExtend;
            word.Selection.EndKey(ref moveUnit, ref moveType);
        }

        private void MoveCursorToLineHome()
        {
            MoveCursorToLineHome(false);
        }

        private void MoveCursorToLineHome(bool extend)
        {
            object moveUnit = Word.WdUnits.wdLine;
            object moveType = Word.WdMovementType.wdMove;
            if (extend)
                moveType = Word.WdMovementType.wdExtend;
            word.Selection.HomeKey(ref moveUnit, ref moveType);
        }

        private float GetCurrentLineHeight(bool isLastLine)
        {
            //select current line
            object moveUnit = Word.WdUnits.wdLine;
            object moveType = Word.WdMovementType.wdMove;
            word.Selection.HomeKey(ref moveUnit, ref moveType);
            moveType = Word.WdMovementType.wdExtend;
            word.Selection.EndKey(ref moveUnit, ref moveType);

            Word.Paragraph para = word.Selection.Paragraphs[1];
            float spacing = 2 * para.LineSpacing;
            if (isLastLine)
            {
                spacing += para.LineUnitAfter;
            }

            Word.Font rangeFont = word.Selection.Range.Font;
            if (rangeFont != null && rangeFont.Size > 0)
            {
                return spacing + GetTotalFontSize(new TataFont(rangeFont));
            }

            float maxHeight = spacing;
            foreach (Word.Range character in word.Selection.Characters)
            {
                Word.Font font = character.Font;
                if (font == null)
                {
                    continue;
                }

                float height = spacing + GetTotalFontSize(new TataFont(font));
                if (height > maxHeight)
                {
                    maxHeight = height;
                }
            }

            return maxHeight;
        }
        #endregion

        protected override void OpenFile()
        {
            OpenApplication();
            object docName = workItem.Source;
            document = word.Documents.Open(ref docName, ref Global.FalseValue,
                                                  ref Global.TrueValue, ref Global.FalseValue, ref Global.NullObject,
                                                  ref Global.NullObject, ref Global.TrueValue, ref Global.NullObject,
                                                  ref Global.NullObject, ref Global.NullObject, ref Global.NullObject,
                                                  ref Global.FalseValue, ref Global.NullObject, ref Global.NullObject,
                                                  ref Global.TrueValue, ref Global.NullObject);

            document.Activate();
        }

        protected override void CloseFile()
        {
            if (document != null)
            {
                ((Word._Document)document).Close(ref Global.FalseValue, ref Global.NullObject, ref Global.NullObject);
                document = null;
            }

            CloseApplication();
        }

        protected override void PrintOut()
        {
            PdfCreator.PrintOut(document);
        }

        protected override void ExtractBookmarks()
        {
            if (document == null || document.Paragraphs.Count == 0)
            {
                return;
            }

            AbsolutePosition.PageWidth = document.PageSetup.PageWidth;
            AbsolutePosition.PageHeight = document.PageSetup.PageHeight;
            bool firstNode = true;
            BookmarkNode prevNode = null;
            Word.WdOutlineLevel prevLevel = Word.WdOutlineLevel.wdOutlineLevel1;
            Word.Range range ;
            AbsolutePosition position;
            char pageBreak = '\f';
            char[] whiteChars = new char[]{' ', '\t', '\n', '\r'};
            string paraText = string.Empty;
            Word.WdOutlineLevel curLevel;
            BookmarkNode node = null;

            Word.Paragraphs paragraphs = document.Paragraphs;
            
            foreach (Word.Paragraph para in paragraphs)
            {
                curLevel = para.OutlineLevel;

                if (curLevel != Word.WdOutlineLevel.wdOutlineLevelBodyText)
                {
                    range = para.Range;
                    GoToCharAt(range.Start);
                    position = new AbsolutePosition(0, SelectionVerticalPosition,
                                                    SelectionPageNo);
                    paraText = range.Text;
                    //remove the leading page break and accordingly, go to next page
                    while (paraText.Length > 0 &&
                        paraText[0] == pageBreak
                    )
                    {
                        GoToNext(wdGoToPage);
                        position = new AbsolutePosition(0, SelectionVerticalPosition,
                                                    SelectionPageNo);
                        paraText = paraText.Substring(1);
                    }

                    paraText = paraText.Trim();
                    if (paraText.Length != 0)
                    {
                        node = CreateBookmarkByPosition(position, paraText, (int)curLevel,
                            new PageSetup(word.Selection.PageSetup));

                        if (firstNode == true)
                        {
                            bookmarks.Add(node.Map);
                            firstNode = false;
                        }
                        else
                        {
                            if (curLevel > prevLevel)
                            {
                                prevNode.AddKid(node);
                            }
                            else if (curLevel == prevLevel)
                            {
                                if (!prevNode.IsTopLevelNode)
                                {
                                    prevNode.ParentNode.AddKid(node);
                                }
                                else
                                {
                                    bookmarks.Add(node.Map);
                                }
                            }
                            else
                            {
                                while (curLevel <= prevLevel)
                                {
                                    if (prevNode.IsTopLevelNode)
                                    {
                                        //it's the top level node, break out
                                        break;
                                    }

                                    prevNode = prevNode.ParentNode;
                                    prevLevel = (Word.WdOutlineLevel) prevNode.Level;
                                }//while

                                if (prevLevel < curLevel)
                                {
                                    prevNode.AddKid(node);
                                }
                                else
                                {
                                    bookmarks.Add(node.Map);
                                }
                            }//else
                        }

                        //update the previous curLevel
                        prevLevel = curLevel;
                        //update previous node
                        prevNode = node;
                    }
                }
            }

        }

        private Rectangle[] GetHyperlinkRectangles(Word.Hyperlink link, ref int linkPageNo)
        {
            System.Collections.Generic.List<Rectangle> rectList = new 
                            System.Collections.Generic.List<Rectangle>();

            float startX, startY, endX, endY;
            Word.Range startPoint = GoToCharAt(link.Range.Start);
            Word.Range endPoint = GetRangeAt(link.Range.End);

            int startPageNo = SelectionPageNo;
            if (startPageNo <= 0)
            {
                return null;
            }

            linkPageNo = startPageNo;

            endPoint.Select();
            int endPageNo = SelectionPageNo;
            while (startPageNo < endPageNo)
            {
                GoToPrevious(wdGoToLine, SelectionLineNo);
                MoveCursorToLineEnd();
                --endPageNo;
            }
            //update end point
            endPoint = word.Selection.Range;
            int endLineNo = SelectionLineNo;
            startPoint.Select();
            int startLineNo = SelectionLineNo;

            Word.PageSetup pageSetup = word.Selection.PageSetup;
            while (startLineNo < endLineNo)
            {
                startX = SelectionHorizontalPosition;
                startY = SelectionVerticalPosition;
                float rightOffset = word.Selection.Paragraphs.RightIndent + pageSetup.RightMargin;
                endX = pageSetup.PageWidth - rightOffset;

                //goto next line
                GoToNext(wdGoToLine);
                endY = SelectionVerticalPosition;

                Rectangle rect = CreateRectangle(startX, endX, startY, endY, 
                                        new PageSetup(pageSetup), Global.TextLinkSpecificOffset);
                if (rect != null)
                {
                    rectList.Add(rect);
                }
                ++startLineNo;
                MoveCursorToLineHome();
            }

            if (word.Selection.Start == endPoint.Start)
            {
                //it's at the beginning of the line or an empty range, nothing to do, return
                return rectList.ToArray();
            }

            startX = SelectionHorizontalPosition;
            startY = SelectionVerticalPosition;

            if (startY == 77.0f)
            {
                startY = 126.5f;
            }

            //go to line end
            MoveCursorToLineEnd();
            if (word.Selection.Start != endPoint.Start)
            {
                //not at the end of the line, select the end point
                //this is a work around for MS Word's positioning bug when at the 
                //end of the line or at the beginning of the line
                endPoint.Select();
            }

            endX = SelectionHorizontalPosition;

            //goto next line
            GoToNext(wdGoToLine);
            endPageNo = SelectionPageNo;
            if (endPageNo > startPageNo)
            {
                endY = startY + GetCurrentLineHeight(false);
            }
            else
            {
                int lastLineNo = (int)endPoint.get_Information(wdFirstLineNo);

                //this is a workaround for MS Word's minor bug annoying positioning bug
                MoveCursorToLineEnd();
                int nextLineNo = SelectionLineNo;
                if (lastLineNo == nextLineNo)
                {
                    //fail to move, means we're at the end of the document
                    endY = startY + GetCurrentLineHeight(true);
                }
                else
                {
                    endY = SelectionVerticalPosition;
                }
            }

            Rectangle lastRect = CreateRectangle(startX, endX, startY, endY,
                                    new PageSetup(pageSetup), Global.TextLinkSpecificOffset);
            if (lastRect != null)
            {
                rectList.Add(lastRect);
            }

            return rectList.ToArray();
        }

        protected override void ExtractHyperlinks()
        {
            if (document == null || document.Hyperlinks.Count == 0)
            {
                return;
            }

            hyperlinks = new ArrayList();

            System.Collections.Generic.List<Rectangle> rectList = new 
                    System.Collections.Generic.List<Rectangle>();
            foreach (Word.Hyperlink hyperlink in document.Hyperlinks)
            {
                rectList.Clear();
                
                int linkPageNo = 0;

                string address;
                try
                {
                    address = hyperlink.Address;
                }
                catch
                {
                    //something went wrong, we simply goto next hyperlink
                    continue;
                }

                switch (hyperlink.Type)
                {
                    case Core.MsoHyperlinkType.msoHyperlinkShape:
                        {
                            Word.Shape shape = hyperlink.Shape;
                            shape.Select(ref Global.NullObject);
                            int pageNo = SelectionPageNo;
                            linkPageNo = pageNo;
                            Word.PageSetup pageSetup = word.Selection.PageSetup;
                            float startX = SelectionHorizontalPosition;
                            float startY = SelectionVerticalPosition;
                            double A = shape.Rotation * Math.PI / 180.0f;
                            double cosA = Math.Abs(Math.Cos(A));
                            double sinA = Math.Abs(Math.Sin(A));
                            float endX = startX +  (float)(shape.Width * cosA + shape.Height * sinA);
                            float endY = startY +  (float)(shape.Height * cosA + shape.Width * sinA);

                            Rectangle rect = CreateRectangle(startX, endX, startY, endY, new PageSetup(pageSetup));
                            if (rect != null)
                            {
                                rectList.Add(rect);
                            }
                        }
                        break;

                    case Core.MsoHyperlinkType.msoHyperlinkRange:
                        {
                            Rectangle[] rects = GetHyperlinkRectangles(hyperlink, ref linkPageNo);
                            if (rects != null)
                            {
                                rectList.AddRange(rects);
                            }
                        }
                        break;
                }//switch

                Hyperlink link = null;
                if (address == null || address.Length == 0)
                {
                    //it's a local destination, bookmark or heading
                    object index = hyperlink.SubAddress;
                    Word.Bookmark bookmark ;
                    try
                    {
                        bookmark = document.Bookmarks.get_Item(ref index);
                    }
                    catch
                    {
                        //invalid bookmark, we continue to next hyperlink
                        continue;
                    }
                    if (bookmark == null)
                    {
                        continue;
                    }
                    Word.Range startPoint = bookmark.Range;
                    startPoint.Select();
                    float Y = SelectionVerticalPosition;
                    CoordinateConverter.ConvertVerticalCoordinate(ref Y, new PageSetup( startPoint.PageSetup) , Global.BookmarkSpecificOffset);
                    link = new LocalDestination(linkPageNo, SelectionPageNo, Y);
                }
                else if (Hyperlink.IsWebLink(address))
                {
                    //it's a web link
                    link = new RemoteLink(linkPageNo, address);
                }
                else if (Hyperlink.IsMailToLink(address))
                {
                    //it's a mail to link
                    link = new MailTo(linkPageNo, address, hyperlink.EmailSubject);
                }
                else
                {
                    try
                    {
                        //it's a remote file
                        FileInfo fileInfo = new FileInfo(document.FullName);
                        string oriWorkingDir = System.Environment.CurrentDirectory;
                        System.Environment.CurrentDirectory = fileInfo.DirectoryName;
                        fileInfo = new FileInfo(address);
                        if (fileInfo.Exists)
                        {
                            address = "file:///" + fileInfo.FullName;
                            address = address.Replace('\\', '/');
                            link = new RemoteLink(linkPageNo, address);
                            System.Environment.CurrentDirectory = oriWorkingDir;
                        }
                    }
                    catch
                    {
                        //it's a dead link, we simply ignore it
                    }
                }

                if (link != null)
                {
                    link.AddRectangles(rectList.ToArray());

                    hyperlinks.Add(link);
                }
            }//for
        }

        protected override void OpenApplication()
        {
            if (!OfficeChecker.WordExists)
            {
                throw new OfficeComponentsMissing(ApplicationName);
            }

            if (word == null)
            {
                word = new Word.ApplicationClass();
                word.Visible = false;
                word.DisplayAlerts = Microsoft.Office.Interop.Word.WdAlertLevel.wdAlertsNone;
            }
        }

        public override void CloseApplication()
        {
            if (word != null)
            {
                ((Word._Application)word).Quit(ref Global.FalseValue, ref Global.NullObject, ref Global.NullObject);
                System.Runtime.InteropServices.Marshal.ReleaseComObject(word);
                word = null;
            }
        }

        #endregion
    }//DocConverter



}//iPdf

#endif